<?php
declare(strict_types=1);

namespace App\Aspect;

use App\Annotation\AuthUser;
use App\Model\User;
use Hyperf\Context\Context;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\HttpServer\Annotation\AutoController;
use Hyperf\HttpServer\Annotation\Controller;
use HyperfExt\Jwt\Contracts\JwtFactoryInterface;
use Psr\Http\Message\ServerRequestInterface;

#[Aspect(annotations: [
    Controller::class,
    AutoController::class,
])]
class AuthUserAspect extends AbstractAspect
{
    #[Inject]
    private JwtFactoryInterface $factory;

    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $token = $this->getToken();
        if (empty($token)) {
            return $proceedingJoinPoint->process();
        }

        $method = $proceedingJoinPoint->getReflectMethod();
        $params = $method->getParameters();
        $arguments = $proceedingJoinPoint->arguments;

        $jwt = $this->factory->make();

        foreach ($params as $param) {
            $attributes = $param->getAttributes(AuthUser::class);

            if (!empty($attributes)) {
                $type = $param->getType();
                if (!$type instanceof \ReflectionNamedType) {
                    throw new \RuntimeException('AuthUser注解需要标注类型：int或者User');
                }

                $typeName = $type->getName();
                $value = null;

                if (!$type->allowsNull()) {
                    throw new \RuntimeException('AuthUser注解需要允许类型为可空类型，例如?int, ?User');
                }

                if ($typeName === 'int') {
                    $value = (int) $jwt->setToken($token)->getPayload()->get('sub');
                } elseif ($typeName === User::class) {
                    $id = $jwt->setToken($token)->getPayload()->get('sub');
                    $value = User::find($id);
                } else {
                    throw new \RuntimeException('AuthUser注解类型错误');
                }

                $arguments['keys'][$param->getName()] = $value;
            }
        }

        $proceedingJoinPoint->arguments = $arguments;

        return $proceedingJoinPoint->process();
    }

    private function getToken()
    {
        if (!Context::has(ServerRequestInterface::class)) {
            return null;
        }

        $request = Context::get(ServerRequestInterface::class);

        $authorization = $request->getHeader('Authorization');
        if (empty($authorization)) {
            return null;
        }

        $token = explode(' ', $authorization[0])[1];
        if (empty($token)) {
            return null;
        }

        return $token;
    }
}